/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.bpm;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.cfg.BpmEngineContext;
import cn.easyplatform.contexts.Contexts;
import cn.easyplatform.contexts.RecordContext;
import cn.easyplatform.dao.impl.mysql.MySqlDialect;
import cn.easyplatform.dao.utils.DaoUtils;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Lang;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.vos.BpmTaskVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snaker.engine.Context;
import org.snaker.engine.SnakerEngine;
import org.snaker.engine.SnakerException;
import org.snaker.engine.access.QueryFilter;
import org.snaker.engine.access.dialect.OracleDialect;
import org.snaker.engine.access.dialect.PostgresqlDialect;
import org.snaker.engine.access.dialect.SQLServerDialect;
import org.snaker.engine.cfg.Configuration;
import org.snaker.engine.core.ServiceContext;
import org.snaker.engine.entity.Process;
import org.snaker.engine.entity.*;
import org.snaker.engine.helper.SnakerHelper;
import org.snaker.engine.impl.GeneralAccessStrategy;
import org.snaker.engine.impl.LogInterceptor;
import org.snaker.engine.impl.RhinoExpression;
import org.snaker.engine.impl.SimpleContext;
import org.snaker.engine.model.NodeModel;
import org.snaker.engine.model.ProcessModel;
import org.snaker.engine.model.TaskModel;
import org.snaker.engine.model.WorkModel;
import org.snaker.engine.parser.impl.*;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cn.easyplatform.dao.utils.DaoUtils.*;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class SnakerEngineContext implements BpmEngineContext {

    private final static Logger log = LoggerFactory.getLogger(SnakerEngineContext.class);

    @Override
    public String getInstanceTask(String processId) {
        SnakerEngine engine = getEngine();
        ProcessModel model = engine.process().getProcessById(processId)
                .getModel();
        String name = model.getStart().getOutputs().get(0).getTo();
        for (WorkModel wm : model.getWorkModels()) {
            if (wm.getName().equals(name))
                return wm.getForm();
        }
        return null;
    }

    @Override
    public void createCCOrder(String orderId, String... actorIds) {
        try {
            getEngine().order().createCCOrder(orderId, actorIds);
        } catch (SnakerException ex) {
            if (log.isErrorEnabled())
                log.error("createCCOrder", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    @Override
    public void updateCCStatus(String orderId, String... actorIds) {
        try {
            getEngine().order().updateCCStatus(orderId, actorIds);
        } catch (SnakerException ex) {
            log.error("updateCCStatus", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    public void startAndExecute(String processId, RecordContext rc) {
        startAndExecute(processId, null, rc);
    }

    public void startAndExecute(String processId, String nodeName, RecordContext rc) {
        Map<String, Object> args = new HashMap<String, Object>();
        for (int name = 870; name <= 874; name++) {
            String value = rc.getParameterAsString(String.valueOf(name));
            if (!Strings.isBlank(value))
                args.put(value, rc.getValue(value));
        }
        if (log.isDebugEnabled())
            log.debug("startAndExecute:{}", processId);
        String operator = rc.getParameterAsString("720");
        SnakerEngine engine = getEngine();
        try {
            Order order = engine.startInstanceById(processId, operator, args);
            // 设置流程实例id以及工作项等信息
            rc.setParameter("863", order.getId());
            List<Task> tasks = engine.query().getActiveTasks(
                    new QueryFilter().setOrderId(order.getId()));
            if (tasks != null && tasks.size() > 0) {
                Task task = tasks.get(0);
                ServiceContext.getContext().set(RecordContext.class, rc);
                tasks = null;
                tasks = engine.executeTask(task.getId(), operator, args);
                if (tasks != null && !tasks.isEmpty()) {
                    task = null;
                    if (!Strings.isBlank(nodeName)) {
                        for (Task t : tasks) {
                            if (nodeName.equals(t.getTaskName()))
                                task = t;
                            else//删除多余的任务
                                engine.task().removeTask(t.getId());
                        }
                    }
                    if (task == null)
                        task = tasks.get(0);
                    rc.setParameter("864", task.getId());
                    rc.setParameter("865", task.getTaskName());
                    rc.setParameter("866", task.getDisplayName());
                    if (task.getModel() instanceof TaskModel)
                        rc.setParameter("867",
                                ((TaskModel) task.getModel()).getAssigneeType());
                }
                rc.setParameter("868", Boolean.FALSE);
            } else
                rc.setParameter("868", Boolean.TRUE);
        } catch (SnakerException ex) {
            log.error("startAndExecute", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    public void executeAndJumpTask(String taskId, String nodeName,
                                   RecordContext rc) {
        String operator = rc.getParameterAsString("720");
        Map<String, Object> args = new HashMap<String, Object>();
        for (int name = 870; name <= 874; name++) {
            String value = rc.getParameterAsString(String.valueOf(name));
            if (!Strings.isBlank(value))
                args.put(value, rc.getValue(value));
        }
        SnakerEngine engine = getEngine();
        ServiceContext.getContext().set(RecordContext.class, rc);
        try {
            List<Task> tasks = engine.executeAndJumpTask(taskId, operator, args, nodeName);
            if (tasks != null && !tasks.isEmpty()) {
                Task task = tasks.get(0);
                rc.setParameter("864", task.getId());
                rc.setParameter("865", task.getTaskName());
                rc.setParameter("866", task.getDisplayName());
                if (task.getModel() instanceof TaskModel)
                    rc.setParameter("867",
                            ((TaskModel) task.getModel()).getAssigneeType());
                rc.setParameter("868", Boolean.FALSE);
            } else
                rc.setParameter("868", Boolean.TRUE);
        } catch (SnakerException ex) {
            log.error("executeAndJumpTask", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    public void execute(String taskId, RecordContext rc) {
        Map<String, Object> args = new HashMap<String, Object>();
        for (int name = 870; name <= 874; name++) {
            String value = rc.getParameterAsString(String.valueOf(name));
            if (!Strings.isBlank(value))
                args.put(value, rc.getValue(value));
        }
        String operator = rc.getParameterAsString("720");
        SnakerEngine engine = getEngine();
        ServiceContext.getContext().set(RecordContext.class, rc);
        try {
            List<Task> tasks = engine.executeTask(taskId, operator, args);
            if (tasks != null && !tasks.isEmpty()) {
                Task task = tasks.get(0);
                rc.setParameter("864", task.getId());
                rc.setParameter("865", task.getTaskName());
                rc.setParameter("866", task.getDisplayName());
                if (task.getModel() instanceof TaskModel)
                    rc.setParameter("867",
                            ((TaskModel) task.getModel()).getAssigneeType());
                rc.setParameter("868", Boolean.FALSE);
            } else
                rc.setParameter("868", Boolean.TRUE);
        } catch (SnakerException ex) {
            log.error("execute", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    @Override
    public void take(String taskId, String operator) {
        try {
            getEngine().task().take(taskId, operator);
        } catch (SnakerException ex) {
            log.error("take", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    public void withdraw(String taskId, String operator) {
        try {
            getEngine().task().withdrawTask(taskId, operator);
        } catch (SnakerException ex) {
            log.error("withdraw", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    public void reject(String taskId, RecordContext rc) {
        SnakerEngine engine = getEngine();
        ServiceContext.getContext().set(RecordContext.class, rc);
        try {
            engine.executeAndJumpTask(taskId, rc.getParameterAsString("720"),
                    null, null);
        } catch (SnakerException ex) {
            log.error("reject", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    public void resignTo(String taskId, RecordContext rc, String... actorIds) {
        resignTo(taskId, 0, rc, actorIds);
    }

    public void resignTo(String taskId, int performType, RecordContext rc,
                         String... actorIds) {
        SnakerEngine engine = getEngine();
        ServiceContext.getContext().set(RecordContext.class, rc);
        try {
            List<Task> tasks = engine.task().createNewTask(taskId, performType,
                    actorIds);
            if (tasks != null && !tasks.isEmpty()) {
                Task task = tasks.get(0);
                rc.setParameter("864", task.getId());
                rc.setParameter("865", task.getTaskName());
                rc.setParameter("866", task.getDisplayName());
                if (task.getModel() instanceof TaskModel)
                    rc.setParameter("867",
                            ((TaskModel) task.getModel()).getAssigneeType());
            }
            engine.task().complete(taskId, rc.getParameterAsString("720"));
        } catch (SnakerException ex) {
            log.error("resignTo", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    public void addTaskActor(String taskId, String... actorIds) {
        try {
            getEngine().task().addTaskActor(taskId, actorIds);
        } catch (SnakerException ex) {
            log.error("addTaskActor", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    @Override
    public void addTaskActor(String taskId, int performType, String... actorIds) {
        try {
            getEngine().task().addTaskActor(taskId, performType, actorIds);
        } catch (SnakerException ex) {
            log.error("addTaskActor", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    public void removeTaskActor(String taskId, String... actorIds) {
        try {
            getEngine().task().removeTaskActor(taskId, actorIds);
        } catch (SnakerException ex) {
            log.error("removeTaskActor", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    @Override
    public void terminate(String orderId, String operator) {
        try {
            getEngine().order().terminate(orderId, operator);
        } catch (SnakerException ex) {
            log.error("terminate", ex);
            String msg = ex.getMessage();
            Throwable throwable = ex.getCause();
            while (throwable != null) {
                msg = throwable.getMessage();
                throwable = throwable.getCause();
            }
            throw new EasyPlatformWithLabelKeyException("bpm.exec.error", msg);
        }
    }

    @Override
    public String[][] getNodes(String processId) {
        SnakerEngine engine = getEngine();
        ProcessModel model = engine.process().getProcessById(processId)
                .getModel();
        List<String[]> result = new ArrayList<String[]>();
        for (WorkModel wm : model.getWorkModels()) {
            String[] node = new String[3];
            node[0] = wm.getName();
            node[1] = wm.getForm();
            node[2] = wm.getDisplayName();
            result.add(node);
        }
        String[][] nodes = new String[result.size()][3];
        result.toArray(nodes);
        return nodes;
    }

    public String[][] getNextNodes(String processId, String nodeName, Map<String, Object> args) {
        SnakerEngine engine = getEngine();
        ProcessModel model = engine.process().getProcessById(processId)
                .getModel();
        List<String[]> result = new ArrayList<String[]>();
        for (WorkModel wm : model.getWorkModels()) {
            if (nodeName.equals(wm.getName()) && wm instanceof TaskModel) {
                TaskModel tm = (TaskModel) wm;
                List<NodeModel> models = tm.getNextTaskModels(args);
                for (NodeModel task : models) {
                    String[] node = new String[2];
                    node[0] = task.getName();
                    node[1] = task.getDisplayName();
                    result.add(node);
                }
                break;
            }
        }
        String[][] nodes = new String[result.size()][3];
        result.toArray(nodes);
        return nodes;
    }

    /**
     * 获取流程中任务结点的参与者，返回参与人、角色、类型
     *
     * @return
     */
    public String[] getActors(String processId, String nodeName) {
        SnakerEngine engine = getEngine();
        ProcessModel model = engine.process().getProcessById(processId)
                .getModel();
        String[] result = new String[3];
        for (WorkModel wm : model.getWorkModels()) {
            if (nodeName.equals(wm.getName())) {
                TaskModel tm = (TaskModel) wm;
                result[0] = tm.getAssignee();
                result[1] = tm.getAssigneeRole();
                result[2] = tm.getAssigneeType();
                return result;
            }
        }
        return result;
    }

    @Override
    public String[] getView(String id, boolean isProcessId) {
        SnakerEngine engine = getEngine();
        if (isProcessId) {
            Process process = engine.process().getProcessById(id);
            ProcessModel model = process.getModel();
            String[] views = new String[2];
            if (model != null)
                views[0] = SnakerHelper.getModelJson(model);
            return views;
        } else {
            HistoryOrder order = engine.query().getHistOrder(id);
            List<Task> tasks = engine.query().getActiveTasks(
                    new QueryFilter().setOrderId(id));
            Process process = engine.process().getProcessById(
                    order.getProcessId());
            ProcessModel model = process.getModel();
            String[] views = new String[2];
            if (model != null)
                views[0] = SnakerHelper.getModelJson(model);
            if (tasks != null && !tasks.isEmpty())
                views[1] = SnakerHelper.getActiveJson(tasks);
            return views;
        }
    }

    @Override
    public BpmTaskVo getTask(String orderId, String taskName) {
        SnakerEngine engine = getEngine();
        List<HistoryTask> tasks = engine.query().getHistoryTasks(
                new QueryFilter().setOrderId(orderId).setName(taskName));
        BpmTaskVo btv = new BpmTaskVo();
        if (!tasks.isEmpty()) {
            HistoryTask task = tasks.get(0);
            btv.setActors(task.getOperator());
            btv.setCreateTime(task.getCreateTime());
            btv.setFinishTime(task.getFinishTime());
        }
        if (btv.getActors() == null) {
            List<Task> activeTask = engine.query().getActiveTasks(
                    new QueryFilter().setOrderId(orderId).setName(taskName));
            if (!activeTask.isEmpty()) {
                Task task = activeTask.get(0);
                String[] actors = engine.query().getTaskActorsByTaskId(
                        task.getId());
                if (actors != null)
                    btv.setActors(Lang.concat(actors).toString());
                btv.setCreateTime(task.getCreateTime());
            }
        }
        return btv;
    }

    private SnakerEngine getEngine() {
        SnakerEngine engine = ServiceContext.getEngine();
        if (engine == null) {
            CommandContext cc = Contexts.getCommandContext();
            DataSource ds = cc.getProjectService().getDataSource();
            Context ctx = new SimpleContext();
            ctx.put("start", StartParser.class);
            ctx.put("task", TaskParser.class);
            ctx.put("custom", CustomParser.class);
            ctx.put("decision", DecisionParser.class);
            ctx.put("subprocess", SubProcessParser.class);
            ctx.put("fork", ForkParser.class);
            ctx.put("join", JoinParser.class);
            ctx.put("end", EndParser.class);
            switch (DaoUtils.getDbType(ds.toString())) {
                case ORACLE:
                    ctx.put("org.snaker.engine.access.dialect.OracleDialect",
                            OracleDialect.class);
                    break;
                case MYSQL:
                    ctx.put("org.snaker.engine.access.dialect.MySqlDialect",
                            MySqlDialect.class);
                    break;
                case SQLSERVER:
                    ctx.put("org.snaker.engine.access.dialect.SQLServerDialect",
                            SQLServerDialect.class);
                    break;
                case PSQL:
                    ctx.put("org.snaker.engine.access.dialect.PostgresqlDialect",
                            PostgresqlDialect.class);
                    break;
            }
            ctx.put("org.snaker.engine.impl.RhinoExpression",
                    RhinoExpression.class);
            ctx.put("org.snaker.engine.impl.GeneralAccessStrategy",
                    GeneralAccessStrategy.class);
            ctx.put("org.snaker.engine.impl.LogInterceptor",
                    LogInterceptor.class);
            engine = new Configuration(ctx).buildSnakerEngine(ds);
            ServiceContext.setEngine(engine);
        }
        return engine;
    }

}
